#!/usr/bin/perl

######################################################################
package Swignition::GenericParser;
######################################################################

# $Id: GenericParser.pm 160 2009-02-22 20:40:35Z mail@tobyinkster.co.uk $

use utf8;
use Swignition::DataModel;
use Swignition::Export::Entity;
use Swignition::GenericParser::DocModel;
use Swignition::GenericParser::eRDF;
use Swignition::GenericParser::GRDDL;
use Swignition::GenericParser::HTTP;
use Swignition::GenericParser::RDFa;
use Swignition::GenericParser::RDF;
use Swignition::GenericParser::Recursive;
use Swignition::GenericParser::RoleAttr;
use Swignition::GenericParser::OpenCalaisML;
use Swignition::GenericParser::Metadata;
use Swignition::GenericParser::Utils;
use Swignition::MagicString;
use Swignition::Misc;
use Swignition::Namespace;
use Swignition::RDFModel::Property;
use Swignition::RDFModel::Subject;
use Swignition::RDFModel::Value;
use Swignition::RDFModel::Smusher;
use Swignition::uF::COinS;
use Swignition::uF::RelEnclosure;
use Swignition::uF::RelLicense;
use Swignition::uF::RelTag;
use Swignition::uF::XFN;
use Swignition::uF::adr;
use Swignition::uF::figure;
use Swignition::uF::geo;
use Swignition::uF::hAlarm;
use Swignition::uF::hAtom;
use Swignition::uF::hAudio;
use Swignition::uF::hCalendar;
use Swignition::uF::hCard;
use Swignition::uF::hEvent;
use Swignition::uF::hMeasure;
use Swignition::uF::hRecipe;
use Swignition::uF::hResume;
use Swignition::uF::hReview;
use Swignition::uF::hTodo;
use Swignition::uF::species;
use Swignition::uF::xoxo;
use URI::URL;
use XML::LibXML qw(:all);
use strict;

# Our more specific child classes...
require Swignition::HtmlParser;
require Swignition::RdfXmlParser;
require Swignition::Notation3Parser;
require Swignition::TurtleParser;
require Swignition::JsonParser;
require Swignition::TrigParser;
require Swignition::FeedParser;
require Swignition::PoxParser;

BEGIN
{
	our $VERSION = 0.1;
	our $XDP;
}

sub new_by_type
{
	my $source = shift;
	my $opts   = shift;
	
	my $mime   = $opts->{response}->header('Content-Type');

	# Use HtmlParser for media types text/html, application/xhtml+xml and
	# application/vnd.wap.xhtml+xml.
	if ($mime =~ m#^\s*(text/html|application/(vnd\.wap\.)?xhtml\+xml)\b#i)
		{ return Swignition::HtmlParser->new($source, $opts); }

	# Use TurtleParser for Turtlish media types, and also for text/plain if it
	# looks like the file is N-Triples.
	if ($mime =~ m#^\s*((text|application)/(rdf\+|x-|x-rdf\+)?turtle)\b#i
	|| ($mime =~ m#^\s*(text/plain)\b# && !($source =~ /^\s*[^_<#]/m)))
		{ return Swignition::TurtleParser->new($source, $opts); }

	# Use Notation3Parser for N3ish media types.
	if ($mime =~ m#^\s*((text|application)/(rdf\+|x-|x-rdf\+)?n3)#i)
		{ return Swignition::Notation3Parser->new($source, $opts); }

	# Use JsonParser for JSONish media types.
	if ($mime =~ m#^\s*((text|application)/(rdf\+|x-|x-rdf\+)?json)#i)
		{ return Swignition::JsonParser->new($source, $opts); }

	# Use RdfXmlParser for media types application/rdf+xml and older text/rdf.
	# Also use it for generic XML media types which look like they might be RDF.
	if ($mime =~ m#^\s*(application/rdf\+xml|text/rdf)\b#i
	|| ($mime =~ m#^\s*((application|text)/xml)\b#i && substr($source, 0, 1024) =~ m#(xmlns\:rdf=)#i))
		{ return Swignition::RdfXmlParser->new($source, $opts); }

	# Use TrigParser for media types application/x-trig.
	if ($mime =~ m#^\s*(application/x-trig)\b#i)
		{ return Swignition::TrigParser->new($source, $opts); }

	# Use FeedParser for RSS, and for XML which contains "<rss" in the first 1024 bytes.
	if ($mime =~ m#^\s*((application|text)/rss(\+xml)?)\b#i
	||  $mime =~ m#^\s*((application|text)/atom(\+xml)?)\b#i
	|| ($mime =~ m#^\s*((application|text)/xml)\b#i && substr($source, 0, 1024) =~ m#<rss\b#i)
	|| ($mime =~ m#^\s*((application|text)/xml)\b#i && substr($source, 0, 1024) =~ m#(http://www\.w3\.org/2005/Atom|http://purl\.org/atom/ns)#i))
		{ return Swignition::FeedParser->new($source, $opts); }

	# Any other media type containing "xml" is parsed as plain old XML.
	if ($mime =~ m#xml#i)
		{ return Swignition::PoxParser->new($source, $opts); }

	# Should we parse TriX and other esoteric RDF formats?
	# SVG should use GRDDL + RDFa ?
	# Otherwise should use a basic module for misc files. Just does "HTTP in RDF"

	return undef;
}


sub find_base_uri
{
	my $this = shift;

	$this->{BASE} = $this->{RESPONSE}->base()
		if ($this->{RESPONSE} && $this->{RESPONSE}->base());
}


sub get_ua
{
	return Swignition::Misc::get_ua;
}


sub mergeNS
# Can be passed either an object with a metadata_ns method or
# a list of suggestedPrefix, url, title.
{
	my $this    = shift;
	my $mergeme = shift;
	
	if (ref $mergeme && UNIVERSAL::can($mergeme, 'metadata_ns'))
	{
		my $mns = $mergeme->metadata_ns;
		foreach my $k (keys %$mns)
		{
			$this->mergeNS($k, $mns->{$k}->{nsurl}, $mns->{$k}->{title}, undef, 0);
		}
	}
	
	else
	{
		my $suggestedPrefix = $mergeme;
		my $NS = Swignition::Namespace->new($suggestedPrefix, @_);
		
		my $addIt = 1;
		foreach my $existing (@{$this->{Namespaces}})
		{
			if (Swignition::Namespace::equiv($existing, $NS))
			{
				$addIt = 0;
				last;
			}
		}
		
		push @{$this->{Namespaces}}, $NS
			if ($addIt);

		return $suggestedPrefix;
	}
}


sub mergeSubject
{
	my $this    = shift;
	my $mergeme = shift;
	my $key     = shift || 'uF';
	my $target;
	
	# Refuse to add boring subjects.
	return unless (defined $mergeme && defined $mergeme->{uris});
	return unless (((keys %{$mergeme->{properties}}) > 1)
			 ||      (1 < scalar(@{ $mergeme->{uris} })));

	if (defined $mergeme && $mergeme->uri =~ m#^http://thing-described-by.org/?\?(.+)$#)
	{
		$mergeme->addVal('http://www.w3.org/2000/01/rdf-schema#isDefinedBy',
			Swignition::RDFModel::Value->new($1, 1));
	}
	
	my $subject_uri = $mergeme->uri;
	$subject_uri =~ s#^bnode:///#_:#;
	my $subject = $this->graph($key)->lookup_node($subject_uri, 1);

	foreach my $property ($mergeme->properties)
	{
		foreach my $value (@{ $mergeme->{'properties'}->{$property}->valObjects })
		{
			my $triple;
			if ($value->type)
			{
				my $value_uri = $value->toString;
				$value_uri =~ s#^bnode:///#_:#;
				$triple = $this->graph($key)->quick_rrr(
					$subject,
					$property, 
					$value_uri);
			}
			else
			{
				$triple = $this->graph($key)->quick_rrl(
					$subject,
					$property,
					$value->toString,
					$value->datatype,
					$value->lang);
			}
				
			$this->graph->add_triple($triple);
		}
	}
}


sub object_structure
{
	my $source = shift;
	my $opts   = shift;
	
	my $this   = {
		'Source'   => $source,
		'UA'       => $opts->{ua},
		'REQUEST'  => $opts->{request},
		'RESPONSE' => $opts->{response},
		'BASE'     => $opts->{base},
		'options'  => {
			'erdf_strict_profiles'  => 1,
			'grddl_fetch'  => 0,
			'grddl_strict_profiles' => 0,
			'normalise_lists'       => 1,
			'nofollow'     => undef,
			'p_AUTO'       => 1,
			'p_comments'   => 1,
			'p_erdf'       => 1,
			'p_grddl'      => 1,
			'p_http'       => 0,
			'p_metatags'   => 1,
			'p_rdf'        => 1,
			'p_rdfa'       => 1,
			'p_rdfx'       => 1,
			'p_role'       => 1,
			'p_structure'  => 0,
			'p_uf'         => 1,
			'rdfa_strict_doctype'   => 0,
			'rdfa_strict_version'   => 0,
			'rdfa_strings' => 0,
			'smush'        => 0,
			'uf_strict_profiles'    => 0,
		},
	};

	# Process options.
	foreach my $o (keys %$opts)
	{
		$this->{options}->{$o} = $opts->{$o}
			if (defined $opts->{$o} && defined $this->{options}->{$o});
	}
	foreach my $h (@{ $opts->{nofollow} })
	{
		$this->{NoFollow}->{$h} = 1;
	}

	# This is a URI representing the currently running instance of Swignition.
	$this->{'instance_uri'} = sprintf('tag:buzzword.org.uk,%s:Swignition-%s/Instances/%d.%d.%06d/',
		$Swignition::Misc::releaseDate, $Swignition::Misc::version, time(), $$, int(rand(999_999)));

	# Create model for graphs.
	$this->{'Data'}->{'model'} = $opts->{'model'} || Swignition::DataModel::Model->new;
	
	# Assign a name to the combined graph.
	$this->{'Data'}->{'model'}->combined_graph->add_uri(graph_name($this, 'all'));

	return $this;
}


sub model
{
	my $this = shift;
	return $this->{'Data'}->{'model'};
}

sub graph
{
	my $this = shift;
	my $name = shift;
	
	$name = 'main' unless length $name;
	$name = $this->graph_name($name) unless ($name =~ /:/);
	
	unless (defined $this->{'Data'}->{ $name })
	{
		$this->{'Data'}->{ $name }
			= Swignition::DataModel::Graph->new($this->{'Data'}->{'model'});
		$this->{'Data'}->{ $name }->add_uri($name);
	}
	
	return $this->{'Data'}->{ $name };
}


sub graph_name
{
	my $this = shift;
	my $name = shift;
	
	return $this->{'instance_uri'} . 'Graphs#' . $name;
}


sub smush
{
#	my $this = shift;
#	return unless ($this->{options}->{p_uf});
#	
#	return Swignition::RDFModel::Smusher::smush($this->{RDF}, $this->{RDF1});
}


sub uri
# This function performs two seemingly different duties. Firstly, if passed a
# parameter, treats this as a (possibly relative) URI and returns that same URI
# as an absolute URI, given $this's known base URI. Secondly, if not passed a
# parameter, will return $this's base URI. However, if you reflect on this, you
# will notice that the latter duty derives directly from the former.
{
	my $this  = shift;
	my $param = shift || '';
	my $opts  = shift || {};
	
	if ('XML::LibXML::Element' eq ref $opts)
	{
		my $x = {'element' => $opts};
		$opts = $x;
	}
	
	if ($param =~ /^([a-z][a-z0-9\+\.\-]*)\:/i)
	{
		# seems to be an absolute URI, so can safely return "as is".
		return $param;
	}
	elsif ($opts->{'require-absolute'})
	{
		return undef;
	}
	
	my $url   = url $param, $this->{BASE};
	return $url->abs->as_string;
}


1;

